package org.apache.ocean.main;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.lang.ArrayUtils;
import org.apache.ocean.main.search.cache.FieldCacheKey.FieldIndex;
import org.apache.ocean.main.search.cache.FieldCacheKey.FieldIndex.ValueIndex;
import org.apache.ocean.main.util.Constants;
import org.apache.ocean.main.util.NullLeastComparator;
import org.apache.ocean.main.util.Util;

public class FieldIndexCreator {
	private final Map<Object, Docs> valueMap;
	private final String fieldName;
	private final boolean docsAsLinkedList;
	private final Class type;

	public FieldIndexCreator(String fieldName, Class type, int potentialSize, boolean docsAsLinkedList) {
		this.fieldName = fieldName;
		this.type = type;
		valueMap = new HashMap<Object, Docs>(potentialSize);
		this.docsAsLinkedList = docsAsLinkedList;
	}

	public FieldIndex getFieldIndex(int maxDoc) {
		ValueIndex[] valueIndices = new ValueIndex[valueMap.size()];
		int x = 0;
		for (Map.Entry<Object, Docs> entry : valueMap.entrySet()) {
			Object value = entry.getKey();
			if (value == Constants.NULL) {
				value = null;
			}
			Docs docs = entry.getValue();
			valueIndices[x] = new ValueIndex((Comparable)value, docs.getDocs());
			x++;
		}
		Arrays.sort(valueIndices, new NullLeastComparator());
		TreeMap<Object,ValueIndex> treeMap = new TreeMap<Object,ValueIndex>();
		for (ValueIndex valueIndex : valueIndices) {
			treeMap.put(valueIndex.value, valueIndex);
		}
		return new FieldIndex(fieldName, type, maxDoc, treeMap);
	}

	public abstract static class Docs {
		public abstract void merge(Object object);
		public abstract int[] getDocs();
	}

	public static class IntArrayDocs extends Docs {
		private int[] intArray;

		public int[] getDocs() {
			return intArray;
		}

		public void merge(Object object) {
			if (object instanceof Integer) {
				if (intArray == null) {
					intArray = new int[] { (Integer) object };
				} else {
					ArrayUtils.add(intArray, (Integer) object);
				}
			} else if (object instanceof int[]) {
				int[] otherIntArray = (int[]) object;
				if (intArray == null) {
					intArray = otherIntArray;
				} else {
					intArray = ArrayUtils.addAll(intArray, otherIntArray);
				}
			}
		}
	}

	public static class LinkedListDocs extends Docs {
		private List<Integer> list = new LinkedList<Integer>();

		public int[] getDocs() {
			return Util.toIntArray(list);
		}

		public void merge(Object object) {
			if (object instanceof Integer) {
				list.add((Integer) object);
			} else if (object instanceof int[]) {
				int[] intArray = (int[]) object;
				for (int doc : intArray) {
					list.add(doc);
				}
			}
		}
	}

	private Object getKey(Object value) {
		if (value == null) {
			return Constants.NULL;
		}
		return value;
	}
  
	private Docs newDocs() {
		if (docsAsLinkedList) {
			return new LinkedListDocs();
		} else {
			return new IntArrayDocs();
		}
	}
	
	public void add(Object value, int[] docsArray) {
		add(value, docsArray);
	}
  
	private void add(Object value, Object incomingDocs) {
		Object key = getKey(value);
		Docs docs = valueMap.get(key);
    if (docs == null) {
    	valueMap.put(key, newDocs());
    }
		docs.merge(incomingDocs);
	}
	
	public void add(Object value, int doc) {
		add(value, doc);
	}
}
